package com.jiangyg.mall.db.mybatis;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jiangyg.mall.core.enums.Logic;
import com.jiangyg.mall.core.enums.Operator;
import com.jiangyg.mall.core.exception.CoreException;
import com.jiangyg.mall.core.support.restful.Criteria;
import com.jiangyg.mall.core.support.restful.Order;
import com.jiangyg.mall.db.mybatis.metadata.Pager;
import com.jiangyg.mall.core.support.restful.Pagination;
import com.jiangyg.mall.core.utils.Assert;
import com.jiangyg.mall.core.utils.Logger;
import com.jiangyg.mall.core.utils.bean.BeanUtils;
import com.jiangyg.mall.core.utils.bean.PropertyUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * 类描述：Service层基础方法
 *
 * @param <M> Mapper接口的Class
 * @param <T> 实体Bean的Class
 * @author jiangyg
 * @version 2.0
 * @date 2020-07-14
 */
@Slf4j
@SuppressWarnings("unchecked")
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> {

    protected final M baseMapper;

    protected final Class<T> entityClazz = currentEntityClass();

    public BaseServiceImpl(M baseMapper) {
        this.baseMapper = baseMapper;
    }

    /**
     * 功能描述：根据泛型获取实体的class
     *
     * @return 返回实体的class
     */
    protected Class<T> currentEntityClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
    }

    /**
     * 功能描述：保存或者修改实体
     *
     * @param dto 待保存的DTO
     * @param <D> DTO的class
     * @return 成功条数
     * @throws Exception 异常
     */
    protected <D> int executeSaveOrUpdate(D dto) throws Exception {
        // 实体初始化
        T entity = entityClazz.newInstance();
        BeanUtils.copyProperties(entity, dto);
        // 获取实体表信息
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClazz);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        // 判断实体是否存在主键
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notNull(keyProperty, "error: can not execute. because can not find column for id from entity!");
        Object idValue = BeanUtils.getProperty(entity, keyProperty);
        // 如果主键不存在则新增
        if (idValue == null || StringUtils.isBlank(idValue.toString()) || Objects.isNull(baseMapper.selectById((Serializable) idValue))) {
            return baseMapper.insert(entity);
        }
        // 修改逻辑
        return baseMapper.updateById(entity);
    }

    /**
     * 功能描述：根据主键查询实体信息
     *
     * @param id    主键
     * @param clazz DTO的class
     * @param <D>   DTO泛型
     * @return 返回DTO用户信息
     * @throws Exception 异常
     */
    protected <D> D executeSelectById(Serializable id, Class<D> clazz) throws Exception {
        // 表主键列名
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClazz);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        // 查询条件
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.eq(tableInfo.getKeyColumn(), id);
        // 执行查询
        T t = this.baseMapper.selectOne(wrapper);
        if (t == null) {
            return null;
        }
        D d = clazz.newInstance();
        BeanUtils.copyProperties(d, t);
        return d;
    }

    /**
     * 功能描述：通用的实体分页查询
     *
     * @param pagination 分页参数
     * @param wrapper    查询条件
     * @param clazz      DTO的class
     * @param <D>        DTO泛型
     * @return 返回分页信息
     * @throws Exception 异常
     */
    protected <D> Pagination<D> executeSelectPagination(Pagination<D> pagination, QueryWrapper<T> wrapper, Class<D> clazz) throws Exception {
        Logger.info(log, () -> String.format("通用的实体[%s]分页查询开始……", entityClazz.getName()));
        // 分页参数初始化
        Pager<T> pager = new Pager<>();
        pager.setSize(pagination.getSize());
        pager.setCurrent(pagination.getCurrent());
        // 分页查询执行
        pager = baseMapper.selectPage(pager, wrapper);
        // 结果集转换（有Entity转换为VO）
        List<D> records = BeanUtils.transformList(pager.getRecords(), clazz);
        pagination.setRecords(records);
        // 查询总条数
        pagination.setTotal(pager.getTotal());
        Logger.info(log, () -> String.format("通用的实体[%s]分页查询结束……", entityClazz.getName()));
        return pagination;
    }

    /**
     * 功能描述：通用的实体分页查询
     *
     * @param pagination 分页参数
     * @param clazz      DTO的class
     * @param <D>        DTO泛型
     * @return 返回分页信息
     * @throws Exception 异常
     */
    protected <D> Pagination<D> executeSelectPagination(Pagination<D> pagination, Class<D> clazz) throws Exception {
        // 构建动态查询条件
        final QueryWrapper<T> queryWrapper = buildQueryWrapper(pagination, Logic.AND, entityClazz);
        // 分页查询执行
        return this.executeSelectPagination(pagination, queryWrapper, clazz);
    }

    /**
     * 功能描述：批量插入
     *
     * @param entityList ignore
     * @param batchSize  ignore
     * @return ignore
     */
    protected boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    /**
     * 功能描述：批量插入或者修改
     *
     * @param entityList ignore
     * @param batchSize  ignore
     * @return ignore
     */
    protected boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClazz);
        com.baomidou.mybatisplus.core.toolkit.Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        com.baomidou.mybatisplus.core.toolkit.Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            Object idVal = ReflectionKit.getMethodValue(entityClazz, entity, keyProperty);
            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.checkValNull(idVal) || Objects.isNull(baseMapper.selectById((Serializable) idVal))) {
                sqlSession.insert(tableInfo.getSqlStatement(SqlMethod.INSERT_ONE.getMethod()), entity);
            } else {
                MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
                param.put(Constants.ENTITY, entity);
                sqlSession.update(tableInfo.getSqlStatement(SqlMethod.UPDATE_BY_ID.getMethod()), param);
            }
        });
    }

    /**
     * 功能描述：批量修改
     *
     * @param entityList ignore
     * @param batchSize  ignore
     * @return ignore
     */
    protected boolean updateBatchById(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

    /**
     * 功能描述：执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param <E>       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    private <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        com.baomidou.mybatisplus.core.toolkit.Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        return !com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(list) && executeBatch(sqlSession -> {
            int size = list.size();
            int i = 1;
            for (E element : list) {
                consumer.accept(sqlSession, element);
                if ((i % batchSize == 0) || i == size) {
                    sqlSession.flushStatements();
                }
                i++;
            }
        });
    }

    /**
     * 功能描述：执行批量操作
     *
     * @param consumer consumer
     * @since 3.3.0
     * @deprecated 3.3.1 后面我打算移除掉 {@link #executeBatch(Collection, int, BiConsumer)} }.
     */
    private boolean executeBatch(Consumer<SqlSession> consumer) {
        SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityClazz);
        SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory);
        boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
        if (sqlSessionHolder != null) {
            SqlSession sqlSession = sqlSessionHolder.getSqlSession();
            //原生无法支持执行器切换，当存在批量操作时，会嵌套两个session的，优先commit上一个session
            //按道理来说，这里的值应该一直为false。
            sqlSession.commit(!transaction);
        }
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        if (!transaction) {
            log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional");
        }
        try {
            consumer.accept(sqlSession);
            //非事物情况下，强制commit。
            sqlSession.commit(!transaction);
            return true;
        } catch (Throwable t) {
            sqlSession.rollback();
            Throwable unwrapped = ExceptionUtil.unwrapThrowable(t);
            if (unwrapped instanceof RuntimeException) {
                MyBatisExceptionTranslator myBatisExceptionTranslator
                        = new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true);
                throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped));
            }
            throw ExceptionUtils.mpe(unwrapped);
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 功能描述：获取分页查询的动态查询条件
     *
     * @param pagination 分页参数
     * @param clazz      Entity的class
     * @return 返回动态查询条件
     */
    protected QueryWrapper<T> buildQueryWrapper(Pagination<?> pagination, Class<T> clazz) {
        return this.buildQueryWrapper(pagination, Logic.AND, clazz);
    }

    /**
     * 功能描述：获取分页查询的动态查询条件
     *
     * @param pagination 分页参数
     * @param logic      逻辑关系
     * @param clazz      Entity的class
     * @return 返回动态查询条件
     */
    protected QueryWrapper<T> buildQueryWrapper(Pagination<?> pagination, Logic logic, Class<T> clazz) {
        List<Criteria> criterias = pagination.getCriterias();
        List<Order> orders = pagination.getOrders();
        // 动态查询条件转换
        final QueryWrapper<T> wrapper = new QueryWrapper<>();
        // 排序
        if (CollectionUtils.isNotEmpty(orders)) {
            orders.forEach(order -> {
                String column = obtainColumnName(clazz, order.getName());
                if (order.isAsc()) {
                    wrapper.orderByAsc(column);
                } else {
                    wrapper.orderByDesc(column);
                }
            });
        }
        if (CollectionUtils.isEmpty(orders) && BeanUtils.hasProperty(clazz, "createTime")) {
            // 没有排序规则，则默认create_time倒排序
            wrapper.orderByDesc("create_time");
        }
        // 如果无动态查询条件，则返回null
        if (CollectionUtils.isEmpty(criterias)) {
            return wrapper;
        }
        Object value;
        Object[] values;
        String name, column;
        for (Criteria criteria : criterias) {
            name = criteria.getName();
            value = criteria.getValue();
            values = criteria.getValues();
            Operator operator = Operator.decide(criteria.getOperator());
            // 判断bean是否有属性及获取列名
            column = obtainColumnName(clazz, name);
            if (StringUtils.isBlank(column)) {
                continue;
            }
            // 操作符默认直等
            if (operator == null) {
                operator = Operator.EQ;
            }
            // 判断查询条件是否生效
            if (Operator.isScope(operator) && values == null) {
                continue;
            }
            if (!Operator.isScope(operator) && (value == null || StringUtils.EMPTY.equals(value))) {
                continue;
            }
            // 根据操作符处理动态查询条件
            final QueryWrapper<T> logicCondition = logic == Logic.OR ? wrapper.or() : wrapper;
            switch (operator) {
                case EQ:
                    logicCondition.eq(column, value);
                    break;
                case GT:
                    logicCondition.gt(column, value);
                    break;
                case GE:
                    logicCondition.ge(column, value);
                    break;
                case LT:
                    logicCondition.lt(column, value);
                    break;
                case LE:
                    logicCondition.le(column, value);
                    break;
                case LK:
                    logicCondition.likeLeft(column, value);
                    break;
                case LL:
                    logicCondition.like(column, value);
                    break;
                case RL:
                    logicCondition.likeRight(column, value);
                    break;
                case IN:
                    if (values.length == 0) {
                        throw new CoreException("动态查询条件类型为in，必须传入values参数且数组长度不能小于等于0！");
                    }
                    logicCondition.in(column, values);
                    break;
                case BT:
                    if (values.length < 2) {
                        throw new CoreException("动态查询条件类型为between，必须传入values参数且数组长度不能小于2！");
                    }
                    logicCondition.between(column, values[0], values[1]);
                    break;
                default:
                    break;
            }
        }
        return wrapper;
    }

    /**
     * 功能描述：获取 SqlStatement
     *
     * @param sqlMethod ignore
     * @return ignore
     */
    private String sqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.table(entityClazz).getSqlStatement(sqlMethod.getMethod());
    }

    /**
     * 功能描述：根据实体属性名获取字段名
     *
     * @param clazz 实体
     * @param name  属性名
     * @return 返回列名
     */
    private String obtainColumnName(Class<T> clazz, String name) {
        // 判断bean是否有属性及获取列名
        Field field = PropertyUtils.getField(clazz, name);
        if (field == null) {
            // throw new CoreException("动态查询条件[" + name + "]在bean[" + clazz.getName() + "]未找到对应属性！");
            return null;
        }
        String column = null;
        TableField tableField = field.getAnnotation(TableField.class);
        if (tableField != null && StringUtils.isNotBlank(tableField.value())) {
            column = tableField.value();
        }
        return column;
    }


}
